/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_batch.cpp - geometry batching
//


#include "r_local.h"


/*
 ==================
 RB_CheckOverflow
 ==================
*/
void RB_CheckOverflow (int numIndices, int numVertices){

	if (backEnd.numIndices + numIndices <= MAX_INDICES && backEnd.numVertices + numVertices <= MAX_VERTICES)
		return;

	RB_RenderBatch();

	if (numIndices > MAX_INDICES)
		Com_Error(false, "RB_CheckOverflow: numIndices > MAX_INDICES (%i > %i)", numIndices, MAX_INDICES);
	if (numVertices > MAX_VERTICES)
		Com_Error(false, "RB_CheckOverflow: numVertices > MAX_VERTICES (%i > %i)", numVertices, MAX_VERTICES);

	RB_CreateBatch(backEnd.entity, backEnd.material, backEnd.registers, backEnd.drawBatch);
}

/*
 ==================
 RB_CreateBatch
 ==================
*/
void RB_CreateBatch (entity_t *entity, material_t *material, const float *registers, void (*drawBatch)()){

	// Set the batch state
	backEnd.entity = entity;
	backEnd.material = material;
	backEnd.registers = registers;

	if (r_skipBatching->integerValue)
		backEnd.allowBatching = false;
	else
		backEnd.allowBatching = true;

	if (backEnd.debugRendering)
		backEnd.allowFastPaths = false;
	else {
		if (r_skipDeforms->integerValue || material->deform == DFRM_NONE)
			backEnd.allowFastPaths = true;
		else
			backEnd.allowFastPaths = false;
	}

	backEnd.numDraws = 0;

	backEnd.indexBuffer = NULL;
	backEnd.indexPointer = backEnd.indices;

	backEnd.vertexBuffer = NULL;
	backEnd.vertexPointer = backEnd.vertices;

	// Set the draw function
	backEnd.drawBatch = drawBatch;
}

/*
 ==================
 RB_RenderBatch
 ==================
*/
void RB_RenderBatch (){

	int		i;

	if (!backEnd.numIndices || !backEnd.numVertices)
		return;

	// Deform geometry
	RB_Deform();

	// Bind the index buffer
	RB_BindIndexBuffer();

	// Bind the vertex buffer
	RB_BindVertexBuffer();

	// Set up the index pointers
	for (i = 0; i < backEnd.numDraws; i++)
		backEnd.drawIndexPointer[i] = INDEX_OFFSET(backEnd.indexPointer, backEnd.drawBaseIndices[i]);

	// Set up the vertex array
	qglVertexPointer(3, GL_FLOAT, sizeof(glVertex_t), GL_VERTEX_XYZ(backEnd.vertexPointer));

	// Draw the batch
	backEnd.drawBatch();

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();

	// Clear the arrays
	backEnd.numIndices = 0;
	backEnd.numVertices = 0;
}


// ============================================================================


/*
 ==================
 RB_BatchSurface

 TODO: fast paths
 ==================
*/
static void RB_BatchSurface (dsGeometry_t *geometry){

	surface_t	*surface = (surface_t *)geometry;
	triangle_t	*triangle;
	glIndex_t	*indices;
	glVertex_t	*vertices, *vertex;
	int			i;

	// Check for overflow
	RB_CheckOverflow(surface->numTriangles * 3, surface->numVertices);

	// Check for fast paths

	// Batch indices
	indices = backEnd.indices + backEnd.numIndices;

	for (i = 0, triangle = surface->triangles; i < surface->numTriangles; i++, triangle++){
		indices[0] = backEnd.numVertices + triangle->index[0];
		indices[1] = backEnd.numVertices + triangle->index[1];
		indices[2] = backEnd.numVertices + triangle->index[2];

		indices += 3;
	}

	backEnd.numIndices += surface->numTriangles * 3;

	// Batch vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	for (i = 0, vertex = surface->vertices; i < surface->numVertices; i++, vertex++){
		vertices[i].xyz = vertex->xyz;
		vertices[i].normal = vertex->normal;
		vertices[i].tangents[0] = vertex->tangents[0];
		vertices[i].tangents[1] = vertex->tangents[1];
		vertices[i].st = vertex->st;
		vertices[i].color = vertex->color;
	}

	backEnd.numVertices += surface->numVertices;
}

/*
 ==================
 RB_BatchModel

 FIXME: it seems as if Quake fixed the texture problem by this check (if (!triangle->facesFront && st->onSeam) st->st[0] += width * 2)

 TODO: fast paths
 ==================
*/
static void RB_BatchModel (dsGeometry_t *geometry){

	aliasMdlSurface_t	*surface = (aliasMdlSurface_t *)geometry;
	aliasMdlTriangle_t	*triangle;
	aliasMdlSt_t		*st;
	aliasMdlVertex_t	*curVertex, *oldVertex;
	glIndex_t			*indices;
	glVertex_t			*vertices;
	float				backLerp;
	int					i;

	// Interpolate frames
	curVertex = surface->vertices + surface->numVertices * backEnd.entity->e.frame;
	oldVertex = surface->vertices + surface->numVertices * backEnd.entity->e.oldFrame;

	if (backEnd.entity->e.frame == backEnd.entity->e.oldFrame)
		backLerp = 0.0f;
	else
		backLerp = backEnd.entity->e.backLerp;

	// Check for overflow
	RB_CheckOverflow(surface->numTriangles * 3, surface->numVertices);

	// Check for fast paths
	if (backEnd.allowFastPaths){

	}

	// Batch indices
	indices = backEnd.indices + backEnd.numIndices;

	for (i = 0, triangle = surface->triangles; i < surface->numTriangles; i++, triangle++){
		indices[0] = backEnd.numVertices + triangle->index[0];
		indices[1] = backEnd.numVertices + triangle->index[1];
		indices[2] = backEnd.numVertices + triangle->index[2];

		indices += 3;
	}

	backEnd.numIndices += surface->numTriangles * 3;

	// Batch vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	if (backLerp == 0.0f){
		// Optimized case
		for (i = 0, st = surface->st; i < surface->numVertices; i++, curVertex++, st++){
			vertices->xyz = curVertex->xyz;
			vertices->normal = curVertex->normal;
			vertices->tangents[0] = curVertex->tangents[0];
			vertices->tangents[1] = curVertex->tangents[1];
			vertices->st = st->st;
			vertices->color.Identity();

			vertices++;
		}
	}
	else {
		// General case
		for (i = 0, st = surface->st; i < surface->numVertices; i++, curVertex++, oldVertex++, st++){
			vertices->xyz = curVertex->xyz + (oldVertex->xyz - curVertex->xyz) * backLerp;
			vertices->normal = curVertex->normal + (oldVertex->normal - curVertex->normal) * backLerp;
			vertices->tangents[0] = curVertex->tangents[0] + (oldVertex->tangents[0] - curVertex->tangents[0]) * backLerp;
			vertices->tangents[1] = curVertex->tangents[1] + (oldVertex->tangents[1] - curVertex->tangents[1]) * backLerp;
			vertices->st = st->st;
			vertices->color.Identity();

			vertices->normal.NormalizeFast();
			vertices->tangents[0].NormalizeFast();
			vertices->tangents[1].NormalizeFast();

			vertices++;
		}
	}

	backEnd.numVertices += surface->numVertices;
}

/*
 ==================
 
 ==================
*/
static void RB_BatchDecal (dsGeometry_t *geometry){

}

/*
 ==================
 RB_BatchSprite
 ==================
*/
static void RB_BatchSprite (dsGeometry_t *geometry){

	glIndex_t	*indices;
	glVertex_t	*vertices;
	glqVec3		lVector, uVector;
	float		s, c;

	rg.pc.dynamicSprite++;
	rg.pc.dynamicIndices += 6;
	rg.pc.dynamicVertices += 4;

	// Check for overflow
	RB_CheckOverflow(6, 4);

	// Compute left and up vectors
	if (backEnd.entity->e.spriteOriented){
		if (backEnd.entity->e.spriteRotation){
			glqMath::SinCos(DEG2RAD(backEnd.entity->e.spriteRotation), s, c);

			s *= backEnd.entity->e.spriteRadius;
			c *= backEnd.entity->e.spriteRadius;

			lVector = backEnd.entity->e.axis[1] * c - backEnd.entity->e.axis[2] * s;
			uVector = backEnd.entity->e.axis[2] * c + backEnd.entity->e.axis[1] * s;
		}
		else {
			lVector = backEnd.entity->e.axis[1] * backEnd.entity->e.spriteRadius;
			uVector = backEnd.entity->e.axis[2] * backEnd.entity->e.spriteRadius;
		}
	}
	else {
		if (backEnd.entity->e.spriteRotation){
			glqMath::SinCos(DEG2RAD(backEnd.entity->e.spriteRotation), s, c);

			s *= backEnd.entity->e.spriteRadius;
			c *= backEnd.entity->e.spriteRadius;

			lVector = backEnd.viewParms.axis[1] * c - backEnd.viewParms.axis[2] * s;
			uVector = backEnd.viewParms.axis[2] * c + backEnd.viewParms.axis[1] * s;
		}
		else {
			lVector = backEnd.viewParms.axis[1] * backEnd.entity->e.spriteRadius;
			uVector = backEnd.viewParms.axis[2] * backEnd.entity->e.spriteRadius;
		}
	}

	// Batch indices
	indices = backEnd.indices + backEnd.numIndices;

	indices[0] = backEnd.numVertices;
	indices[1] = backEnd.numVertices + 1;
	indices[2] = backEnd.numVertices + 3;
	indices[3] = backEnd.numVertices + 3;
	indices[4] = backEnd.numVertices + 1;
	indices[5] = backEnd.numVertices + 2;

	backEnd.numIndices += 6;

	// Batch vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	vertices[0].xyz = backEnd.entity->e.origin + lVector + uVector;
	vertices[0].st.Set(0.0f, 0.0f);
	vertices[0].color.Identity();

	vertices[1].xyz = backEnd.entity->e.origin - lVector + uVector;
	vertices[1].st.Set(1.0f, 0.0f);
	vertices[1].color.Identity();

	vertices[2].xyz = backEnd.entity->e.origin - lVector - uVector;
	vertices[2].st.Set(1.0f, 1.0f);
	vertices[2].color.Identity();

	vertices[3].xyz = backEnd.entity->e.origin + lVector - uVector;
	vertices[3].st.Set(0.0f, 1.0f);
	vertices[3].color.Identity();

	backEnd.numVertices += 4;
}

/*
 ==================
 RB_BatchBeam
 ==================
*/
static void RB_BatchBeam (dsGeometry_t *geometry){

	glIndex_t	*indices;
	glVertex_t	*vertices;
	glqVec3		view, side, dir;
	float		scale, texScale;

	rg.pc.dynamicBeam++;
	rg.pc.dynamicIndices += 6;
	rg.pc.dynamicVertices += 4;

	// Check for overflow
	RB_CheckOverflow(6, 4);

	// Compute view and side vectors
	view = backEnd.viewParms.origin - backEnd.entity->e.origin;
	side = backEnd.entity->e.beamEnd - backEnd.entity->e.origin;

	// Compute direction
	dir.Cross(view, side);
	dir.NormalizeFast();

	// Compute scale
	scale = backEnd.entity->e.beamWidth * 0.5f;

	// Compute texture scale
	if (!backEnd.entity->e.beamLength)
		texScale = side.LengthFast() / 100.0f;
	else
		texScale = side.LengthFast() / backEnd.entity->e.beamLength;

	// Batch indices
	indices = backEnd.indices + backEnd.numIndices;

	indices[0] = backEnd.numVertices;
	indices[1] = backEnd.numVertices + 1;
	indices[2] = backEnd.numVertices + 3;
	indices[3] = backEnd.numVertices + 3;
	indices[4] = backEnd.numVertices + 1;
	indices[5] = backEnd.numVertices + 2;

	backEnd.numIndices += 6;

	// Batch vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	vertices[0].xyz = backEnd.entity->e.origin + dir * scale;
	vertices[0].st.Set(0.0f, 0.0f);
	vertices[0].color.Identity();

	vertices[1].xyz = backEnd.entity->e.beamEnd + dir * scale;
	vertices[1].st.Set(texScale, 0.0f);
	vertices[1].color.Identity();

	vertices[2].xyz = backEnd.entity->e.beamEnd - dir * scale;
	vertices[2].st.Set(texScale, 1.0f);
	vertices[2].color.Identity();

	vertices[3].xyz = backEnd.entity->e.origin - dir * scale;
	vertices[3].st.Set(0.0f, 1.0f);
	vertices[3].color.Identity();

	backEnd.numVertices += 4;
}

/*
 ==================
 
 ==================
*/
static void RB_BatchParticle (dsGeometry_t *geometry){

}

/*
 ==================
 RB_BatchNull
 ==================
*/
static void RB_BatchNull (dsGeometry_t *geometry){

	glIndex_t	*indices;
	glVertex_t	*vertices;
	glqMat3		axis;
	int			i;

	// Check for overflow
	RB_CheckOverflow(36, 24);

	// Batch indices
	for (i = 0; i < 6; i++){
		indices = backEnd.indices + backEnd.numIndices;

		indices[0] = backEnd.numVertices + (i << 2);
		indices[1] = backEnd.numVertices + (i << 2) + 1;
		indices[2] = backEnd.numVertices + (i << 2) + 3;
		indices[3] = backEnd.numVertices + (i << 2) + 3;
		indices[4] = backEnd.numVertices + (i << 2) + 1;
		indices[5] = backEnd.numVertices + (i << 2) + 2;

		backEnd.numIndices += 6;
	}

	// Batch vertices
	for (i = 0; i < 6; i++){
		// Compute axes
		axis[0].Clear();

		if (i & 1)
			axis[0][i>>1] = -1.0f;
		else
			axis[0][i>>1] = 1.0f;

		axis[0].NormalVectors(axis[1], axis[2]);

		axis[0] *= 16.0f;
		axis[1] *= 16.0f;
		axis[2] *= 16.0f;

		// Batch vertices
		vertices = backEnd.vertices + backEnd.numVertices;

		vertices[0].xyz = axis[0] - axis[1] - axis[2];
		vertices[0].st.Set(0.0f, 1.0f);
		vertices[0].color.Identity();

		vertices[1].xyz = axis[0] + axis[1] - axis[2];
		vertices[1].st.Set(1.0f, 1.0f);
		vertices[1].color.Identity();

		vertices[2].xyz = axis[0] + axis[1] + axis[2];
		vertices[2].st.Set(1.0f, 0.0f);
		vertices[2].color.Identity();

		vertices[3].xyz = axis[0] - axis[1] + axis[2];
		vertices[3].st.Set(0.0f, 0.0f);
		vertices[3].color.Identity();

		backEnd.numVertices += 4;
	}
}

/*
 ==================
 RB_BatchGeometry
 ==================
*/
void RB_BatchGeometry (dsType_t type, dsGeometry_t *geometry){

	if (!backEnd.allowBatching){
		RB_RenderBatch();

		RB_CreateBatch(backEnd.entity, backEnd.material, backEnd.registers, backEnd.drawBatch);
	}

	switch (type){
	case DS_SURFACE:
		RB_BatchSurface(geometry);
		break;
	case DS_MODEL:
		RB_BatchModel(geometry);
		break;
	case DS_DECAL:
		RB_BatchDecal(geometry);
		break;
	case DS_SPRITE:
		RB_BatchSprite(geometry);
		break;
	case DS_BEAM:
		RB_BatchBeam(geometry);
		break;
	case DS_PARTICLE:
		RB_BatchParticle(geometry);
		break;
	case DS_NULL:
		RB_BatchNull(geometry);
		break;
	default:
		Com_Error(false, "RB_BatchGeometry: bad surface type (%i)", type);
	}
}